package dk.kapetanovic.jaft.transaction;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import dk.kapetanovic.jaft.action.Action;
import dk.kapetanovic.jaft.exception.InconsistentStateException;
import dk.kapetanovic.jaft.exception.TransactionException;
import dk.kapetanovic.jaft.log.Journal;

public class TransactionImpl implements Transaction {
	
	private TransactionManager tm;
	private boolean shouldCommit = false;
	private boolean active = true;
	private boolean commited = false;
	
	private Journal journal;
	
	private List<Exception> cleanupExceptions;
	private List<Exception> closeExceptions;
	private boolean error = false;
	private Action failedAction = null;

	public TransactionImpl(TransactionManager tm, Journal journal) {
		this.tm = tm;
		this.journal = journal;
	}
	
	@Override
	public void commit()  throws InconsistentStateException,
			TransactionException {
		if(!active)
			return;
		
		List<Action> actions;
		try {
			actions = journal.commit();
			for(Action a : actions)
				a.close();
		}
		catch(TransactionException e) {
			rollback();
			throw e;
		}
		catch (Exception e) {
			rollback();
			throw new TransactionException(e);
		}
		
		cleanupExceptions = cleanup(actions);
		commited = true;
		active = false;
		if(cleanupExceptions.size() != 0)
			error = true;
		journal.close();
	}

	@Override
	public void end() throws InconsistentStateException, TransactionException {
		if(shouldCommit)
			commit();
		else
			rollback();
	}

	@Override
	public List<Exception> getCleanupExceptions() {
		return cleanupExceptions;
	}

	@Override
	public List<Exception> getCloseExceptions() {
		return closeExceptions;
	}

	@Override
	public TransactionManager getTransactionManager() {
		return tm;
	}

	@Override
	public boolean isActive() {
		return active;
	}
	
	@Override
	public boolean isCommited() {
		return commited;
	}

	@Override
	public Object registerAction(Action action) throws InconsistentStateException, 
			TransactionException {
		if(!active)
			return null;
		
		try {
			action.prepare();
		} 
		catch(Exception e) {
			rollback();
			throw new TransactionException(e);
		}
		
		try {
			journal.addAction(action);
		}
		catch(TransactionException e) {
			failedAction = action;
			undoAction(action);
			rollback();
			throw e;
		}
		catch (Exception e) {
			failedAction = action;
			undoAction(action);
			rollback();
			throw new TransactionException(e);
		}
		
		try {
			return action.execute();
		}
		catch(Exception e) {
			rollback();
			throw new TransactionException(e);
		}
	}

	@Override
	public void rollback() throws InconsistentStateException,
			TransactionException {
		if(!active)
			return;
		
		boolean flag = false;
		ArrayList<Action> actions = new ArrayList<Action>();
		closeExceptions = new ArrayList<Exception>();
		
		int size = journal.size();
		for(int i = 0; i < size; i++) {
			try {
				Action action = journal.undo();
				actions.add(0, action);
				action.close();
			}
			catch(TransactionException e) {
				flag = true;
			}
			catch(InconsistentStateException e) {
				throw e;
			}
			catch(Exception e) {
				closeExceptions.add(e);
			}
		}
		
		cleanupExceptions = cleanup(actions);
		cleanupException(failedAction);
		commited = false;
		active = false;
		if(cleanupExceptions.size() != 0 || closeExceptions.size() != 0)
			error = true;
		
		if(flag)
			throw new TransactionException("Journal repported an error " + 
				"during rollback. Journal may be corrupted. " + 
				"The system is in consistent state.");
		journal.close();
	}

	@Override
	public boolean checkError() {
		return error;
	}

	@Override
	public void setTransactionManager(TransactionManager manager) {
		this.tm = manager;
	}

	@Override
	public void shouldCommit(boolean commit) {
		this.shouldCommit = commit;
	}
	
	private void undoAction(Action a) throws InconsistentStateException {
		try {
			a.undo();
		} 
		catch (IOException e) {
			throw new InconsistentStateException(e);
		}
	}
	
	private void cleanupException(Action a) {
		if(a == null)
			return;
		
		try {
			a.cleanup();
		}
		catch(Exception e) {
			if(cleanupExceptions == null)
				cleanupExceptions = new ArrayList<Exception>();
			
			cleanupExceptions.add(e);
			error = true;
		}
	}
	
	private List<Exception> cleanup(List<Action> actions) {
		ArrayList<Exception> exceptions = new
			ArrayList<Exception>();
		for(Action act : actions) {
			try {
				act.cleanup();
			}
			catch(Exception e) {
				exceptions.add(e);
			}
		}

		return exceptions;
	}
}
